﻿// //  ********************************************************************************
// //  Copyright (C) 2016 Sebastian Kenter. All rights reserved.
// // 
// //  Name    : MultiServerEngine | ServerFramework | ServerPeer.cs
// //  Created : 2016 - 02 - 16 20:53
// //  Modified: 2016 - 02 - 20 12:33
// //  ********************************************************************************

using System;
using System.Collections.Generic;
using System.Linq;
using ExitGames.Logging;
using Photon.SocketServer;
using Photon.SocketServer.ServerToServer;
using PhotonHostRuntimeInterfaces;
using ServerFramework.Implementation.Operations.Request;
using ServerFramework.Implementation.Telegram;
using ServerFramework.Interfaces.Application;
using ServerFramework.Interfaces.Handler;
using ServerFramework.Interfaces.Server;
using ServerFramework.Interfaces.Telegram;
using TypeDefs;

namespace ServerFramework.Implementation.Server
{
    /// <summary>
    ///     Class ServerPeer.
    /// </summary>
    public class ServerPeer : ServerPeerBase, IServerPeer
    {
        public IServerApplication Server { get; }
        private IServerPeerRegistry<IServerPeer> ServerPeerRegistry { get; }
        private ITelegramHandlerRegistry TelegramHandlerRegistry { get; }
        public ILogger Logger { get; }
        public Guid PeerId { get; }
        public ServerTypes ServerType => Server.ServerType;
        private Dictionary<HandlerTypes, List<OperationCodes>> HandledOperations { get; set; }

        public ServerPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer, ILogger logger, IServerApplication server,
                          IServerPeerRegistry<IServerPeer> serverPeerRegistry,
                          ITelegramHandlerRegistry telegramHandlerRegistry) : base(protocol, unmanagedPeer)
        {
            Logger = logger;
            Server = server;
            ServerPeerRegistry = serverPeerRegistry;
            TelegramHandlerRegistry = telegramHandlerRegistry;
            PeerId = Guid.NewGuid();
            HandledOperations = Server.GetHandledOperations();
            Logger.InfoFormat("Created new server peer with id={0} on server={1}", PeerId, ServerType);

            Connect();
        }

        public void Connect()
        {
            // Are we the master peer? If so we cannot register to ourself
            if (Server.GetIsMasterPeer()) return;

            Logger.InfoFormat("Sending register request for server type={0} to MasterServer", ServerType);
            RequestFiber.Enqueue(RegisterServerPeer);
        }

        public Dictionary<HandlerTypes, List<OperationCodes>> GetHandledOperations()
        {
            return HandledOperations;
        }

        private void RegisterServerPeer()
        {
            RegisterSubServerRequestOperation operation = new RegisterSubServerRequestOperation
                                                          {
                                                              SubCode = (int) OperationSubCodes.RegisterSubServer,
                                                              PeerId = PeerId.ToString(),
                                                              PeerType = (byte) ServerType,
                                                              Configuration = Server.GetConfiguration()
                                                          };

            OperationRequest request = new OperationRequest((byte) OperationCodes.System, operation);
            SendOperationRequest(request, new SendParameters());
        }

        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            // Fill up the further used variables with the correct values
            const TelegramTypes type = TelegramTypes.Request;

            byte code = operationRequest.OperationCode;

            int? subCode = operationRequest.Parameters.ContainsKey((byte) ParameterCodes.SubCode)
                ? Convert.ToInt32(operationRequest.Parameters[(byte) ParameterCodes.SubCode])
                : (int?) null;

            // Does our server handle the requested operation?
            if (Server.GetHandledOperations()[HandlerTypes.ServerHandler].Contains((OperationCodes) operationRequest.OperationCode))
            {
                Logger.InfoFormat(
                    "Received OperationRequest from server type={0} with code={1}, sub-code={2} on server={3}",
                    operationRequest.Parameters[(byte) ParameterCodes.PeerType], (OperationCodes) code,
                    (OperationSubCodes) (subCode.HasValue ? Convert.ToInt32(subCode.Value) : Convert.ToInt32(null)),
                    ServerType);

                // Do we have a handler for that operation?
                if (!HandledOperations[HandlerTypes.ServerHandler].Contains((OperationCodes) code)) return;

                ITelegramHandler<IServerPeer> handler = TelegramHandlerRegistry.GetServerHandler(type, code, subCode);

                if (handler.HandleTelegram(new RequestTelegram(code, subCode, operationRequest.Parameters), this))
                    return;

                Logger.ErrorFormat(
                    "Unable to handle OperationRequest from server type={0} with code={1}, sub-code={2} on server={3}",
                    operationRequest.Parameters[(byte) ParameterCodes.PeerType], (OperationCodes) code,
                    (OperationSubCodes) (subCode.HasValue ? Convert.ToInt32(subCode.Value) : Convert.ToInt32(null)),
                    ServerType);
            }
            else
            {
                Logger.InfoFormat(
                    "Received OperationRequest from server type={0} with code={1}, sub-code={2} on server={3} not handled by this server.",
                    (ServerTypes) (int) operationRequest.Parameters[(byte) ParameterCodes.PeerType], code,
                    (OperationSubCodes) (subCode.HasValue ? Convert.ToInt32(subCode.Value) : Convert.ToInt32(null)),
                    ServerType);

                // Try to get a server peer that is capable of handling the operation
                ServerPeer peer = ServerPeerRegistry.GetServerPeers()
                    .Values.FirstOrDefault(
                        c => c.GetHandledOperations()[HandlerTypes.ServerHandler].Contains((OperationCodes) code)) as
                    ServerPeer;

                // Does such a server peer exist? => forward
                if (peer != null)
                {
                    Logger.InfoFormat("Found server {0} that can handle telegram. Forwarding....", peer.ServerType);
                    peer.SendTelegram(new RequestTelegram(code, subCode, operationRequest.Parameters));
                }
                else
                {
                    Logger.ErrorFormat("Cannot find server to handle request!");
                }
            }
        }

        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
        }

        protected override void OnEvent(IEventData eventData, SendParameters sendParameters)
        {
            const TelegramTypes type = TelegramTypes.Event;
            byte code = eventData.Code;
            int? subCode = eventData.Parameters.ContainsKey((byte) ParameterCodes.SubCode)
                ? Convert.ToInt32(eventData.Parameters[(byte) ParameterCodes.SubCode])
                : (int?) null;

            Logger.InfoFormat("Received Event with code={0}, sub-code{1} on server={2}", code, subCode, PeerId);

            ITelegramHandler<IServerPeer> handler = TelegramHandlerRegistry.GetServerHandler(type, code, subCode);

            if (handler.HandleTelegram(new EventTelegram(code, subCode, eventData.Parameters), this)) return;

            Logger.ErrorFormat("Unable to handle event.");
        }

        protected override void OnOperationResponse(OperationResponse operationResponse, SendParameters sendParameters)
        {
            const TelegramTypes type = TelegramTypes.Response;
            byte code = operationResponse.OperationCode;
            int? subCode = operationResponse.Parameters.ContainsKey((byte) ParameterCodes.SubCode)
                ? Convert.ToInt32(operationResponse.Parameters[(byte) ParameterCodes.SubCode])
                : (int?) null;

            Logger.InfoFormat("Received OperationResponse with code={0}, sub-code{1} on server={2}", code, subCode,
                PeerId);

            ITelegramHandler<IServerPeer> handler = TelegramHandlerRegistry.GetServerHandler(type, code, subCode);

            handler?.HandleTelegram(
                new ResponseTelegram(code, subCode, operationResponse.Parameters, operationResponse.ReturnCode,
                    operationResponse.DebugMessage), this);

            //Logger.ErrorFormat("Unable to handle response.");
        }

        public void SendTelegram(ITelegram telegram)
        {
            ResponseTelegram response = telegram as ResponseTelegram;

            if (response != null)
            {
                SendOperationResponse(
                    new OperationResponse(telegram.Code, telegram.Parameters)
                    {
                        DebugMessage = response.DebugMessage,
                        ReturnCode = response.ReturnCode
                    },
                    new SendParameters());
            }
            else if (telegram is RequestTelegram)
            {
                SendOperationRequest(new OperationRequest(telegram.Code, telegram.Parameters), new SendParameters());
            }
            else if (telegram is EventTelegram)
            {
                SendEvent(new EventData(telegram.Code, telegram.Parameters), new SendParameters());
            }
        }

        public void SetHandledOperations(Dictionary<HandlerTypes, List<OperationCodes>> code)
        {
            HandledOperations = code;
        }
    }
}
